<html>
<head>
  <title>23.访问者（Visitor）模式</title>
  <basefont face="微软雅黑" size="2" />
  <meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
  <meta name="exporter-version" content="YXBJ Windows/602628 (zh-CN, DDL); Windows/10.0.0 (Win64); EDAMVersion=V2;"/>
  <style>
    body, td {
      font-family: 微软雅黑;
      font-size: 10pt;
    }
  </style>
</head>
<body>
<a name="27075"/>
<h1>23.访问者（Visitor）模式</h1>
<div>
<table bgcolor="#D4DDE5" border="0">
<tr><td><b>创建时间：</b></td><td><i>2020/10/20 15:57</i></td></tr>
<tr><td><b>更新时间：</b></td><td><i>2021/3/11 1:26</i></td></tr>
<tr><td><b>作者：</b></td><td><i>YuGe</i></td></tr>
</table>
</div>
<br/>

<div>
<span><div><div><span style="font-size: 12pt; font-weight: bold;">一、基本描述</span></div><ul><li><div><span style="font-size: 16px;">表示一个作用于某对象结构中的各元素的操作。它使你可以在不改变各元素的类的前提下定义作用于这些元素的新操作。</span></div></li><li><div><span style="font-size: 16px;">访问者模式算是Gof中最复杂的模式。</span></div></li><li><div><span style="font-size: 16px;">GoF的一个作者说过：</span><span style="font-size: 16px; font-weight: bold;">大多数时候你并不需要访问者模式，但当一旦你需要访问者模式时，那就是真的需要它了</span><span style="font-size: 16px;">。</span></div></li><li><div><span style="font-size: 16px;">访问者模式的动机是以不同的方式操作复杂的对象结构。</span><span style="font-size: 12pt;">访问者模式是一种较为复杂的行为型设计模式，它包含访问者和被访问元素两个主要组成部分，这些被访问的元素通常具有不同的类型，且不同的访问者可以对它们进行不同的访问操作。</span></div></li><li><div><span style="font-size: 12pt;">访问者必须参观组合内的每个元素；这样的功能是在导游</span><span style="font-size: 12pt; color: unset; font-family: unset;">（ Traverser）对象中，访问者通过导游的引导，收集组合中所有对象的状态。一旦状态被收集了，客户就可以让访问者对状态进行各种操作。当需要新的功能时，只要加强访问者即可。【个人感觉这段话对于理解访问者模式非常重要！】【图中Element中的Accept接口的意思是，我这里可以接受Visitor访问。Visitor的中的VisitElementX接口的意思是，我在这里访问Element，这样Visitor就将</span><span style="font-size: 12pt; color: unset; font-family: unset;">Element中的状态收集到了，然后就可以在Visitor中做一些事情！个人感觉可以另外做，也可以在VisitElementX中做！这已经是变体了，而且还是更向本质侧的变体！</span><span style="font-size: 12pt; color: unset; font-family: unset;">】</span></div></li><li><div><span style="font-size: 16px;"><span style="font-size: 16px; font-weight: bold;">处理方案</span></span></div></li><ul><li><div><span style="font-size: 16px;">访问者模式将数据结构和作用于结构之上的操作之间的耦合解脱开，使得操作集合可以相对自由的演化。</span></div></li><li><div><span style="font-size: 16px;">访问者模式的目的是要把处理从数据结构分离出来，很多系统可以按照算法和数据结构分开，如果这样的系统有比较稳定的数据结构，又有易于变化的算法时，使用访问者模式是比较合适的，因为访问者模式使得算法操作的增加变得容易。反之，如果这样的系统的数据结构对象易于变化，经常要有新的数据结构对象增加进来，就不适合使用访问者模式。</span></div></li><li><div><span style="font-size: 16px;">将所有的算法操作分散到各种节点类中会导致整个系统难以理解、难以维护和修改。增加新的操作通常需要重新编译所有这些类，如果可以独立的增加新的操作，并且使这些节点类独立于作用于其上的操作，将会更好一些。</span></div></li><li><div><span style="font-size: 12pt;">可以将每一个类中相关的操作包装在一个独立的对象（称为一个 Visitor）中，并在遍历抽象语法树时将此对象传递给当前访问的元素。当一个元素“接受”该访问者时，该元素向访问者发送一个包含自身类信息的请求。该请求同时也将该元素本身作为一个参数。然后访问者将为该元素执行该操作——这一操作以前是在该元素的类中的。</span></div></li><li><div><span style="font-size: 16px;">使用 Visitor 模式，必须定义两个类层次：一个</span><span style="font-size: 12pt;">是访问者层次结构，</span><span style="font-size: 16px;">对应于接受操作的元素，</span><span style="font-size: 12pt;">提供了抽象访问者和具体访问者。</span><span style="font-size: 16px;">另一个</span><span style="font-size: 12pt;">是元素层次结构，</span><span style="font-size: 16px;">对应于定义对元素的操作的访问者</span><span style="font-size: 12pt;">，提供了抽象元素和具体元素</span><span style="font-size: 16px;">。</span><span style="font-size: 12pt;">相同的访问者可以以不同的方式访问不同的元素，相同的元素可以接受不同访问者以不同访问方式访问。</span></div></li><li><div><span style="font-size: 16px;">给访问者类增加一个新的子类即可创建一个新的操作。</span><span style="font-size: 12pt;">增加新的访问者无须修改原有系统，系统具有较好的可扩展性。</span></div></li></ul><li><div><span style="font-size: 16px; font-weight: bold;">双分派（double-dispatch）</span><span style="font-size: 16px;">。</span><span style="font-size: 12pt;">访问者模式允许你不改变类即可有效地增加其上的操作。</span><span style="font-size: 12pt; color: unset; font-family: unset;">为达到这一效果使用了一种称为</span><span style="font-size: 12pt; color: unset; font-family: unset; font-weight: bold;">双分派（double-dispatch）</span><span style="font-size: 12pt; color: unset; font-family: unset;">的技术。这是一种很著名的技术。</span><span style="font-size: 12pt; color: unset; font-family: unset;">事实上，一些编程语言甚至直接支持这一技术。而象 C++ 这样的语言支持</span><span style="font-size: 12pt; color: unset; font-family: unset; font-weight: bold;">单分派（single-dispatch）</span><span style="font-size: 12pt; color: unset; font-family: unset;">。</span></div></li><ul><li><div><span style="font-size: 12pt; color: unset; font-family: unset;"><span style="font-size: 12pt; color: unset; font-family: unset; font-weight: bold;">单分派：</span>在单分派语言中，到底由哪一种操作将来实现一个请求取决于两个方面：该请求的名字和接收者的类型。</span><span style="font-size: 12pt; color: unset; font-family: unset;">最终哪个操作得到执行依赖于请求的种类和接收者的类型两个方面。</span><span style="font-size: 12pt;">单分派语言处理一个操作是根据请求者的名称和接收到的参数决定的，在Java中有静态绑定和动态绑定之说，它的实现是依据重载（overload）和覆写（override）实现的。</span></div></li><li><div><span style="font-size: 12pt; color: unset; font-family: unset;"><span style="font-size: 12pt; color: unset; font-family: unset; font-weight: bold;">双分派：</span>双分派意味着得到执行的操作取决于请求的种类和两个接收者的类型。 Accept 是一个 double-dispatch 操作。它的含义决定于两个类型：Visitor 的类型和 Element 的类型。双分派使得访问者可以对每一个类的元素请求不同的操作。</span><span style="font-size: 12pt; color: unset; font-family: unset;">双分派意味着得到执行的操作决定于请求的种类和两个接收者的类型，它是多分派的一个特例。Java是一个支持双分派的单分派语言。</span></div></li><li><div><span style="font-size: 12pt; color: unset; font-family: unset;"><span style="font-size: 12pt; color: unset; font-family: unset; font-weight: bold;">这是 Visitor 模式的关键所在：</span>得到执行的操作不仅取决于 Visitor 的类型还决定于它访问的 Element 的类型。可以不将操作静态地绑定在 Element 接口中，而将其安放在一个 Visitor中，并</span><span style="font-size: 12pt; color: unset; font-family: unset;">使用 Accept 在运行时进行绑定。扩展 Element 接口就等于定义一个新的 Visitor 子类而不是多个新的 Element 子类。</span></div></li></ul><li><div><span style="font-size: 12pt;">如果要在系统中增加一种新的访问者，无须修改源代码，只要增加一个新的具体访问者类即可，在该具体访问者中封装了新的操作元素对象的方法。</span><span style="font-size: 12pt; font-weight: bold;">从增加新的访问者的角度来看，访问者模式符合“开闭原则”</span><span style="font-size: 12pt;">。</span></div></li></ul><div style="margin-left: 40px;"><span style="font-size: 12pt;">如果要在系统中增加一种新的具体元素，由于原有系统并未提供相应的访问接口，因此必须对原有系统进行修改，在原有的抽象访问者类和具体访问者类中增加相应的访问方法。</span><span style="font-size: 12pt; font-weight: bold;">从增加新的元素的角度来看，访问者模式违背了“开闭原则”</span><span style="font-size: 12pt;">。</span></div><div style="margin-left: 40px;"><span style="font-size: 12pt;">综上所述，<span style="font-size: 12pt; font-weight: bold;">访问者模式与抽象工厂模式类似，对“开闭原则”的支持具有倾斜性，可以很方便地添加新的访问者，但是添加新的元素较为麻烦。</span></span></div><ul><li><div><span style="font-size: 16px;"><span style="font-size: 16px;"><span style="font-size: 16px; font-weight: bold;">适用性</span></span></span></div></li><ul><li><div><span style="font-size: 12pt;">一个对象结构包含很多类对象，它们有不同的接口，而你想对这些对象实施一些依赖于其具体类的操作。</span><span style="font-size: 12pt;">也就说是用迭代器模式已经不能胜任的情景。</span><span style="font-size: 12pt;">在访问者中针对每一种具体的类型都提供了一个访问操作，不同类型的对象可以有不同的访问操作。</span></div></li><li><div><span style="font-size: 12pt;">需要对一个对象结构中的对象进行很多不同的并且不相关的操作，而你想避免让这些操作“污染”这些对象的类，也不希望在增加新的操作时修改这些类。 访问者模式使得我们可以将相关的操作集中起来定义在访问者类中。</span><span style="font-size: 12pt;">对象结构可以被多个不同的访问者类所使用，将对象本身与对象的访问操作分离。</span><span style="font-size: 12pt;">当该对象结构被很多应用共享时，用 Visitor 模式让每个应用仅包含需要用到的操作。</span></div></li><li><div><span style="font-size: 16px;">访问者模式适用于数据结构相对稳定的系统。</span><span style="font-size: 12pt;">定义对象结构的类很少改变，但经常需要在此结构上定义新的操作。改变对象结构类需要重定义堆所有访问者的接口，这可能需要很大的代价。如果对象结构类经常改变，那么可能还是在这些类中定义这些操作较好。</span></div></li><li><div><span style="font-size: 12pt;">总结一下，在这种地方你一定要考虑使用访问者模式：业务规则要求遍历多个不同的对象。这本身也是访问者模式出发点，迭代器模式只能访问同类或同接口的数据，而访问者模式是对迭代器模式的扩充，可以遍历不同的对象，然后执行不同的操作，也就是针对访问的对象不同，执行不同的操作。</span></div></li><li><div><span style="font-size: 12pt;">访问者模式是一种集中规整模式，特别适用于大规模重构的项目，在这一个阶段需求已经非常清晰，原系统的功能点也已经明确，通过访问者模式可以很容易把一些功能进行梳理，达到最终目的——功能集中化。</span></div></li></ul><li><div><span style="font-size: 16px;"><span style="font-size: 16px; font-weight: bold;">实现考虑</span></span></div></li><ul><li><div><span style="font-size: 12pt;">每一个对象结构将有一个相关的 Visitor 类。这个抽象的访问者类为定义对象结构的每一个 ConcreteElement 类声明一个 VisitConcreteElement 操作。每一个 Visitor 上的 Visit 操作声明它的参数为一个特定的 ConcreteElement，以允许该 Visitor 直接访问 ConcreteElement 的接口。</span><span style="font-size: 12pt; color: unset; font-family: unset;">ConcreteVistor 类重定义每一个 Visit 操作，从而为相应的 ConcreteElement 类实现与特定访问者相关的行为。</span></div></li></ul></ul><div style="margin-left: 80px;"><span style="font-size: 12pt;">每个 ConcreteElement 类实现一个 Accept 操作，这个操作调用访问者中相应于本 ConcreteElement 类的 Visit 的操作。这样最终得到调用的操作不仅依赖于该元素的类也依赖于访问者的类。</span></div><ul><ul><li><div><span style="font-size: 12pt;">谁负责遍历对象结构。</span><span style="font-size: 12pt;">一个访问者必须访问这个对象结构的每一个元素。我们可以将遍历的责任放到下面三个地方中的任意一个：对象结构中，访问者中，或一个独立的迭代器对象中。</span></div></li><ul><li><div><span style="font-size: 12pt;">通常由对象结构负责迭代。一个集合只需对它的元素进行迭代，并对每一个元素调用 Accept 操作。而一个复合通常让 Accept 操作遍历该元素的各子构件并对它们中的每一个递归地调用 Accept。</span></div></li><li><div><span style="font-size: 12pt;">另一个解决方案是使用一个迭代器来访问各个元素。</span></div></li><li><div><span style="font-size: 12pt;">甚至可以将遍历算法放在访问者中，尽管这样将导致对每一个聚合 ConcreteElement，在每一个 ConcreteVisitor 中都要复制遍历的代码。将该遍历策略放在访问者中的主要原因是想实现一个特别复杂的遍历，它依赖于对该对象结构的操作结果。</span></div></li></ul><li><div><span style="font-size: 16px;">可以同组合模式(Composite)联用：访问者可以用于对一个由 Composite 模式定义的对象结构进行操作。</span></div></li><li><div><span style="font-size: 16px;">可以同解释器模式（Interpreter）联用：访问者可以用于解释。</span></div></li><li><div><span style="font-size: 16px;">通常 ConcreteVisitor 可以单独开发，不必跟 ConcreteElementA 或 ConcreteElementB 写在一起，正因为这样，ConcreteVisitor 能提高 ConcreteElement 之间的独立性，如果将一个处理动作设计成 ConcreteElementA 和 ConcreteElementB 类的方法，每次想新增处理以扩充功能时，就要去修改 </span><span style="font-size: 16px;">ConcreteElementA 和 ConcreteElementB 了。</span></div></li></ul><li><div><span style="font-size: 16px;"><span style="font-size: 16px; font-weight: bold;">实际应用</span></span></div></li><ul><li><div><span style="font-size: 12pt;">《大话设计模式》：</span><span style="font-size: 16px;">事实上，我们很难找到数据结构不变化的情况，所以用访问者模式的机会也就不太多了。访问者模式的能力与复杂性是把双刃剑，只有当你真正需要它时，才考虑使用它。有很多程序员为了展示自己面向对象的能力或是沉迷于模式当中，往往会误用这个模式，所以一定要好好理解它的适用性。</span></div></li></ul></ul><div style="margin-left: 80px;"><span style="font-size: 12pt;">《设计模式之禅》：</span><span style="font-size: 12pt;">访问者模式是经常用到的模式，虽然你不注意，有可能你起的名字也不是什么Visitor，但是它确实是非常容易使用到的。</span></div><div style="margin-left: 80px;"><span style="font-size: 12pt;">《Design Pattern Java》：</span><span style="font-size: 12pt;">由于访问者模式的使用条件较为苛刻，本身结构也较为复杂，因此在实际应用中使用频率不是特别高。当系统中存在一个较为复杂的对象结构，且不同访问者对其所采取的操作也不相同时，可以考虑使用访问者模式进行设计。</span></div><div style="margin-left: 80px;"><span style="font-size: 12pt;">【这一点上《设计模式之禅》和《大话设计模式》这两本书描述不一致，</span><span style="font-size: 12pt;">《设计模式之禅》的就是上面这个说法，是访问者模式经常用到，但是</span><span style="font-size: 12pt;">《大话设计模式》说访问者模式并不是经常用到。个人比较偏向</span><span style="font-size: 12pt;">《大话设计模式》当中的说法，访问者模式在需要的时候是真有用，但是在绝大多数场景下，并没有那么适用。</span><span style="font-size: 12pt;">】</span></div><ul><ul><li><div><span style="font-size: 12pt;">访问者模式还可以充当过滤器或者拦截器（Interceptor）角色。</span></div></li><li><div><span style="font-size: 12pt;">在XML文档解析、编译器的设计、复杂集合对象的处理等领域访问者模式得到了一定的应用。</span></div></li><li><div><span style="font-size: 16px;">当你想要为一个对象的组合增加新的能力，且封装并不重要时，就使用访问者模式。</span></div></li></ul></ul><div><span style="font-size: 12pt; font-weight: bold;">二、图例</span></div><div style="text-align: center;"><img src="23.访问者（Visitor）模式_files/Image.png" type="image/png" data-filename="Image.png" width="432"/></div><ul><li><div><span style="font-size: 16px; font-weight: bold;">Visitor（抽象访问者）：</span></div></li><ul><li><div><span style="font-size: 12pt;">为该对象结构中 ConcreteElement 的每一个类声明一个 Visit 操作。该操作的名字和特征标识了发送 Visit 请求给该访问者的那个类。这使得访问者可以确定正被访问元素的具体的类。这样访问者就可以通过该元素的特定接口直接访问它。</span></div></li><li><div><span style="font-size: 16px;">抽象类或者接口，声明放着这可以访问哪些元素，具体到程序中就是 Visit 方法的参数定义哪些对象可以被访问的【实际上就是确定接口】</span></div></li><li><div><span style="font-size: 12pt;">抽象访问者定义了访问元素对象的方法，通常为每一种类型的元素对象都提供一个访问方法，而具体访问者可以实现这些访问方法。这些访问方法的命名一般有三种方式：</span></div></li><ul><li><div><span style="font-size: 12pt;">一种是直接在方法名中标明待访问元素对象的具体类型，如visitElementA(ElementAelementA)；</span></div></li><li><div><span style="font-size: 12pt;">还有一种是统一取名为visit()，通过参数类型的不同来定义一系列重载的visit()方法。</span><span style="font-size: 16px;">有几个元素就有几个访问方法，可以利用重载函数来实现接口：都是 visit 方法，传入的参数不一样。</span></div></li><li><div><span style="font-size: 12pt;">当然，如果所有的访问者对某一类型的元素的访问操作都相同，则可以将操作代码移到抽象访问者类中。</span></div></li></ul></ul><li><div><span style="font-size: 16px; font-weight: bold;">ConcreteVisitor（具体访问者）：</span></div></li><ul><li><div><span style="font-size: 12pt;">实现每个由 Visitor 声明的操作。每个操作实现本算法的一部分，而该算法片断乃是对应于结构中对象的类。 ConcreteVisitor 为该算法提供了上下文并存储它的局部状态。</span><span style="font-size: 12pt; color: unset; font-family: unset;">这一状态常常在遍历该结构的过程中累积结果。</span></div></li><li><div><span style="font-size: 16px;">影响访问者访问到一个类后怎么干，要做什么事情。每一个操作用于访问对象结构中的一种类型的元素。</span></div></li><li><div><span style="font-size: 16px;">个人理解</span></div></li><ul><li><div><span style="font-size: 16px;">算法：一个 ConcreteVisitor 是一个算法。</span></div></li><li><div><span style="font-size: 16px;">算法片段：ConcreteVisitor 中的一个函数。</span></div></li><li><div><span style="font-size: 16px;">结构中对象的类：一个 ConcreteVisitor 的一个方法对应一个 ConcreteElement 对象。</span></div></li><li><div><span style="font-size: 16px;">这里的结构指的是对象结构： ObjectStructure，即元素的集合。</span></div></li><li><div><span style="font-size: 16px;">遍历该结构：遍历的是 ObjectStructure </span></div></li></ul></ul><li><div><span style="font-size: 16px;"><span style="font-size: 16px; font-weight: bold;">Element（抽象元素）：</span></span></div></li><ul><li><div><span style="font-size: 16px;">定义一个 Accept 操作，它以一个访问者为参数。</span></div></li><li><div><span style="font-size: 12pt;">接口或抽象类，声明接受哪一类访问者访问，程序上是通过 accept 方法中的参数来定义的。</span></div></li><li><div><span style="font-size: 16px;">抽象元素有两类方法：</span></div></li><ul><li><div><span style="font-size: 16px;">一是本身的业务逻辑，也就是元素作为一个业务处理单元必须完成的职责（Operation）。</span></div></li><li><div><span style="font-size: 16px;">另一个是允许哪一个访问者来访问（Accept），这一方法充分利用双分派技术，实现处理与数据结构的分离。</span></div></li></ul></ul></ul><div style="margin-left: 120px;"><span style="font-size: 16px;">Accept 方法的意义就是说我这个类可以允许谁来访问，也就是定义一类访问者，在具体的实现类中调用访问者的方法。</span></div><div style="margin-left: 120px;"><span style="font-size: 12pt;">需要注意的是该方法（</span><span style="font-size: 16px;">Accept 方法</span><span style="font-size: 12pt;">）传入了一个抽象访问者 Visitor 类型的参数，即针对抽象访问者进行编程，而不是具体访问者，在程序运行时再确定具体访问者的类型，并调用具体访问者对象的 visit() 方法实现对元素对象的操作。在抽象元素类 Element 的子类中实现了 accept() 方法，用于接受访问者的访问，在具体元素类中还可以定义不同类型的元素所特有的业务方法。</span></div><ul><li><div><span style="font-size: 16px;"><span style="font-size: 16px; font-weight: bold;">ConcreteElement（具体元素）：</span></span></div></li><ul><li><div><span style="font-size: 16px;">实现 Accept 操作，该操作以一个访问者为参数。</span></div></li><li><div><span style="font-size: 12pt;">在具体元素类 ConcreteElement 的 accept() 方法中，通过调用 Visitor 类的 visit() 方法实现对元素的访问，并以当前对象作为 visit() 方法的参数。其具体执行过程如下（双重分派）：</span></div></li><ul><li><div><span style="font-size: 12pt;">调用具体元素类的 accept(Visitor visitor) 方法，并将 Visitor 子类对象作为其参数；</span></div></li><li><div><span style="font-size: 12pt;">在具体元素类 accept(Visitor visitor) 方法内部调用传入的 Visitor 对象的 visit() 方法，如 visit(ConcreteElement element)，将当前具体元素类对象(this)作为参数，如 visitor.visit(this)；</span></div></li><li><div><span style="font-size: 12pt;">执行 Visitor 对象的 visit() 方法，在其中还可以调用具体元素对象的业务方法。</span></div></li></ul></ul><li><div><span style="font-size: 12pt;"><span style="font-size: 12pt; font-weight: bold;">ObjectStructure（对象结构）：</span></span></div></li><ul><li><div><span style="font-size: 16px;">能枚举它的元素。</span></div></li><li><div><span style="font-size: 16px;">可以提供一个高层的接口以允许该访问者访问它的元素。</span></div></li><li><div><span style="font-size: 16px;">可以是一个组合（组合模式）或是一个集合，如一个列表或一个无序集合。</span><span style="font-size: 16px;">用于存放元素对象，并且提供了遍历其内部元素的方法，可以结合组合模式实现，也可以是一个简单的集合对象。</span></div></li><li><div><span style="font-size: 16px;">仅仅有 Element 还不行，还需要有一个程序将其组装成一个集合。</span><span style="font-size: 16px;">在使用访问者模式时，被访问元素通常不是单独存在的，它们存储在一个集合中，这个集合被称为“对象结构”，访问者通过遍历对象结构实现对其中存储的元素的逐个操作。</span></div></li><li><div><span style="font-size: 16px;">元素产生者，一般容纳在多个不同类，不同接口的容器，如 List、Map 等，在项目中，可能不用抽象出这个角色。</span></div></li><li><div><span style="font-size: 16px;">一般情况下，</span><span style="font-size: 12pt;">在对象结构中可以使用迭代器对存储在集合中的元素对象进行遍历，并逐个调用每一个对象的accept()方法，实现对元素对象的访问操作。</span></div></li></ul><li><div><span style="font-size: 12pt; font-weight: bold;">协作：</span></div></li><ul><li><div><span style="font-size: 12pt;">一个使用 Visitor 模式的客户必须创建一个 ConcreteVisitor 对象，然后遍历该对象结构，并用该访问者访问每一个元素。</span></div></li><li><div><span style="font-size: 16px;">当一个元素</span><span style="font-size: 12pt;">被访问时，它调用对应于它的类的 Visitor 操作。如果必要，该元素将自身作为这个操作的一个参数以便该访问者访问它的状态。</span></div></li><li><div><span style="font-size: 16px;">下图描述了一个对象结构、一个访问者和两个元素之间的协作。</span></div></li></ul></ul><div style="text-align: center;"><img src="23.访问者（Visitor）模式_files/Image [1].png" type="image/png" data-filename="Image.png" width="362"/></div><div><span style="font-size: 12pt; font-weight: bold;">三、优缺点</span></div><div><span style="font-size: 12pt; font-weight: bold;">优点</span></div><ul><li><div><span style="font-size: 12pt;"><span style="font-size: 12pt; font-weight: bold;">访问者模式使得易于增加新的操作</span>。访问者使得增加依赖于复杂对象结构的构件的操作变得容易了。仅需增加一个新的访问者即可在一个对象结构上定义一个新的操作。相反，如果每个功能都分散在多个类之上的话，定义新的操作时必须修改每一类。</span></div></li></ul><div style="margin-left: 40px;"><span style="font-size: 12pt; color: unset; font-family: unset;"><span style="font-size: 12pt; color: unset; font-family: unset; font-weight: bold;">具有优秀的扩展性</span>。增加新的操作就意味着增加一个新的访问者，访问者模式将有关的行为集中到一个访问者对象中。</span><span style="font-size: 12pt;">通过增加访问者，只要是具体元素就非常容易访问，对元素的遍历就更加容易了，甭管它是什么对象，只要它在一个容器中，都可以通过访问者来访问，任务集中化。由于职责分开，继续增加对数据的操作是非常快捷的。</span></div><ul><li><div><span style="font-size: 12pt;"><span style="font-size: 12pt; font-weight: bold;">访问者集中相关的操作而分离无关的操作</span>。相关的行为不是分布在定义该对象结构的各个类上，而是集中在一个访问者中。无关行为却被分别放在它们各自的访问者子类中。这就既简化了这些元素的类，也简化了在这些访问者中定义的算法。所有与它的算法相关的数据结构都可以被隐藏在访问者中。</span></div></li></ul><div style="margin-left: 40px;"><span style="font-size: 12pt; font-weight: bold;">符合单一职责原则</span><span style="font-size: 12pt;">。</span><span style="font-size: 12pt;">具体元素角色 ConcreteElement 负责数据的加载，而 Visitor 类则负责算法的展现，两个不同的职责非常明确地分离开来，各自演绎变化。</span></div><div style="margin-left: 40px;"><span style="font-size: 12pt;">类的职责更加清晰，有利于对象结构中元素对象的复用，相同的对象结构可以供多个不同的访问者访问。</span></div><ul><li><div><span style="font-size: 12pt;"><span style="font-size: 12pt; font-weight: bold;">通过类层次进行访问</span>。一个迭代器</span><span style="font-size: 12pt;">可以通过调用节点对象的特定操作来遍历整个对象结构，同时访问这些对象。但是迭代器不能对具有不同元素类型的对象结构进行操作。访问者没有这种限制，它可以访问不具有相同父类的对象。可以对一个 Visitor 接口增加任何类型的对象。</span></div><div><span style="font-size: 12pt;">让用户能够在不修改现有元素类层次结构的情况下，定义作用于该层次结构的操作。</span></div></li><li><div><span style="font-size: 12pt;"><span style="font-size: 12pt; font-weight: bold;">累积状态</span>。当访问者访问对象结构中的每一个元素时，它可能会累积状态。如果没有访问者，这一状态将作为额外的参数传递给进行遍历的操作，或者定义为全局变量。</span></div></li><li><div><span style="font-size: 12pt;">灵活性非常高。</span></div></li><li><div><span style="font-size: 12pt;">允许你对组合结构加入新的操作，而无需改变结构本身。</span></div></li><li><div><span style="font-size: 12pt;">访问者所进行的操作，其代码是集中在一起的。【都集中到访问者类中】</span></div></li></ul><div><span style="font-size: 12pt; font-weight: bold;">缺点</span></div><ul><li><div><span style="font-size: 12pt;"><span style="font-size: 12pt; font-weight: bold;">增加新的 ConcreteElement 类很困难</span>。Visitor 模式使得难以增加新的 Element 的子类。每添加一个新的 ConcreteElement 都要在 Vistor 中添加一个新的抽象操作，并在每一个 ConcretV</span><span style="font-size: 12pt; color: unset; font-family: unset;">isitor 类中实现相应的操作。有时可以在 Visitor 中提供一个缺省的实现，这一实现可以被大多数的 ConcreteVisitor 继承，但这与其说是一个规律还不如说是一种例外。</span></div></li></ul><div style="margin-left: 40px;"><span style="font-size: 12pt; color: unset; font-family: unset;"><span style="font-size: 12pt; color: unset; font-family: unset; font-weight: bold;">所以在应用访问者模式时考虑关键的问题是系统的哪个部分会经常变化，是作用于对象结构上的算法呢还是构成该结构的各个对象的类</span>。如果老是有新的 ConcretElement 类加入进来的话，Vistor 类层次将变得难以维护。在这种情况下，直接在构成该结构的类中定义这些操作可能更容易一些。如果 Element类层次是稳定的，而你不断地增加操作获修改算法，访问者模式可以帮助你管理这些改动。</span></div><div style="margin-left: 40px;"><span style="font-size: 12pt;"><span style="font-size: 12pt; font-weight: bold;">具体元素变更比较困难</span>。</span><span style="font-size: 12pt;">具体元素角色的增加、删除、修改都是比较困难的。<br/></span><span style="font-size: 12pt;">因为游走的功能牵涉其中，所以对组合结构的改变就更加困难</span></div><ul><li><div><span style="font-size: 12pt;"><span style="font-size: 12pt; font-weight: bold;">破坏封装</span>。访问者方法假定 ConcreteElement 接口的功能足够强，足以让访问者进行它们的工作。结果是，该模式常常迫使你提供访问元素内部状态的公共操作，这可能会破坏它的封装性。</span></div></li></ul><div style="margin-left: 40px;"><span style="font-size: 12pt;"><span style="font-size: 12pt; font-weight: bold;">具体元素对访问者公布细节</span>。</span><span style="font-size: 12pt;">访问者模式要求访问者对象访问并调用每一个元素对象的操作，</span><span style="font-size: 12pt;">这意味着元素对象有时候必须暴露一些自己的内部操作和内部状态，</span><span style="font-size: 12pt;">访问者要访问一个类就必然要求这个类公布一些方法和数据，也就是说访问者关注了其他类的内部细节，</span><span style="font-size: 12pt;">否则无法供访问者访问。</span><span style="font-size: 12pt;">这是迪米特法则所不建议的。<br/></span><span style="font-size: 12pt;">当采用访问者模式的时候，就会打破组合类的封装。</span></div><ul><li><div><span style="font-size: 12pt;"><span style="font-size: 12pt; font-weight: bold;">违背了依赖倒置转原则</span>。</span><span style="font-size: 12pt;">访问者依赖的是具体元素，而不是抽象元素，这破坏了依赖倒置原则，特别是在面向对象的编程中，抛弃了对接口的依赖，而直接依赖实现类，扩展比较难。</span></div></li></ul><div><span style="font-size: 12pt; font-weight: bold;">四、可能的改进</span></div><ul><li><div><span style="font-size: 16px;">在实际的项目中，一个对象，多个访问者的情况非常多。</span></div></li><li><div><span style="font-size: 12pt;"><a href="https://github.com/PhycheLiu/DesignPattern_Study-Test/tree/master/Java/src/com/company/visitor/sample01" style="font-size: 12pt;">可以利用“xml + 反射”机制改进观察者模式。在 xml 当中指定使用哪个具体观察者。</a></span></div></li></ul><div><span style="font-size: 12pt; font-weight: bold;">五、个人理解</span></div><ul><li><div><span style="font-size: 16px;">访问者模式比较复杂，但不会经常使用，针对元素固定的场景可以考虑。</span></div></li><li><div><span style="font-size: 16px;">双分派的引入让元素与操作互相依赖，除非自动生成代码，否则手写观察者模式感觉并不好。</span></div></li></ul><div><br clear="none"/></div><div><br clear="none"/></div><div><br clear="none"/></div><div><br clear="none"/></div><div><br clear="none"/></div><div><br clear="none"/></div><div><br clear="none"/></div><div><br clear="none"/></div><div><br clear="none"/></div><div><br/></div><div><br/></div><div><br/></div></div><div><br/></div></span>
</div></body></html> 